#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "censusmaster.h"
#include "master.h"
#include "network.h"
#include "stream.h"

struct Slave
{
	struct Address address; 
	int state;
};

struct Slaves
{
	size_t count;
	struct Slave * list;
};

int continueJobber = 0;
struct Slaves slaves;
pthread_mutex_t mutexSlaves;
void (*callback)(int * state, struct Envelope * received, struct Envelope * send) = 0;


struct Slave * addSlave(struct Address * address)
{
	struct Slave * result = 0;
	
	pthread_mutex_lock(&mutexSlaves);
	
	// Find a Slave with the same Address
	size_t i;
	for(i = 0; i < slaves.count; ++i)
	{
		if(compareAddress(&(slaves.list[i].address), address))
		{
			break;
		}
	}
	
	// If no Slave with the same Address were found
	if(i >= slaves.count)
	{
		// Grow List
		if(slaves.count > 0)
		{
			struct Slave * temp = slaves.list;
			slaves.list = malloc(sizeof(struct Slave) * (slaves.count + 1));
			memcpy(slaves.list, temp, sizeof(struct Slave) * slaves.count);
			free(temp);
		}
		else
		{
			slaves.list = malloc(sizeof(struct Slave));
		}
		slaves.count += 1;
		
		// Initialize the newly added Slave Member Variables
		slaves.list[slaves.count].state = 0;
		memcpy(&(slaves.list[slaves.count].address), address, sizeof(struct Address));
		
		// Return the newly added Slave
		result = &(slaves.list[slaves.count]);
	}
	
	pthread_mutex_unlock(&mutexSlaves);
	
	return result;
}

void removeSlave(struct Slave * slave)
{
	pthread_mutex_lock(&mutexSlaves);
	
	// Find a Slave
	size_t i;
	for(i = 0; i < slaves.count; ++i)
	{
		if(slave == &(slaves.list[i]))
		{
			break;
		}
	}
	
	// If Slave was found
	if(i < slaves.count)
	{
		// Shrink List
		if(slaves.count > 1)
		{
			struct Slave * temp = slaves.list;
			slaves.list = malloc(sizeof(struct Slave) * (slaves.count - 1));
			memcpy(slaves.list, temp, sizeof(struct Slave) * i);
			memcpy(slaves.list + i, temp + i + 1, sizeof(struct Slave) * (slaves.count - i - 1));
			free(temp);
		}
		else
		{
			free(slaves.list);
			slaves.list = 0;
		}
		slaves.count -= 1;
	}
	
	pthread_mutex_unlock(&mutexSlaves);
}

void jobber(void * slavePtr)
{
	struct Slave * slave = slavePtr;
	
	struct Socket socket;
	struct Stream * incomingStream = createStream();
	struct Stream * outgoingStream = createStream();
	struct Envelope * envelopeReceived = 0;
	struct Envelope * envelopeToSend = 0;
	
	if(0 == createSocketTCP(&socket, 0))
	{
		if(0 == bindSocket(&socket))
		{
			while(continueJobber)
			{
				envelopeToSend = createEnvelope(0, 0, 0);
				callback(&(slave->state), envelopeReceived, envelopeToSend);
				destroyEnvelope(envelopeReceived);
				serializeEnvelope(envelopeToSend, outgoingStream);
				destroyEnvelope(envelopeToSend);
				
				if(0 == sendStream(&socket, outgoingStream))
				{
					if(0 == receiveStream(&socket, incomingStream))
					{
						envelopeReceived = deserializeEnvelope(incomingStream);
					}
				}
			}
		}
	}
	
	destroyEnvelope(envelopeReceived);
	destroyEnvelope(envelopeToSend);
	destroyStream(incomingStream);
	destroyStream(outgoingStream);
	
	removeSlave(slave);
}

void slaveFound(struct Address * address)
{
	printf("Slave ");
	printAddress(address);
	printf(": Found\n");
	
	pthread_t threadJobber;	
	pthread_create(&threadJobber, 0, (void *)&jobber, addSlave(address));
}

void startMaster(void (*cb)(int * state, struct Envelope * received, struct Envelope * send))
{
	if(cb == 0)
	{
		printf("WARNING: startMaster() received a null pointer. Cannot start the master.");
	}
	else
	{
		continueJobber = 1;
		slaves.count = 0;
		slaves.list = 0;
		callback = cb;
		
		pthread_mutex_init(&mutexSlaves, 0);
		
		startCensusMaster(&slaveFound);
	}
}

void stopMaster()
{
	continueJobber = 0;
	
	stopCensusMaster();
}
